// INCLUDES -------------------------------------------------------------------------
#include "raytracer.h"	// project

#include "stdio.h"		// printf()
#include "string.h"		// mem[set/cpy/cmp]()
#include "stdlib.h"		// calloc/free

// VARIABLE DECLARATIONS ------------------------------------------------------------

typedef enum
{
	MAX_STACK_SIZE = 1000,
	MAX_PIXEL_SIZE = 4096
} framebufferLimits;

framebuffer fStack[MAX_STACK_SIZE];		// stack of single framebuffers

bool stackIsReady = false;				// initialization flag for framebuffer stack
int currentStackPosition = 0;			// stack position for framebufferCreate()

// FUNCTIONS ------------------------------------------------------------------------

void framebufferInitStack()
{
	// depending on the state of the stack, check if it needs to be initalized
	if (stackIsReady == true) return; // NO, it doesn't...

	// YES, it does! loop through all the stack positions and initialize them
	int i;
	for (i = 0; i < MAX_STACK_SIZE; i++)
	{
	  fStack[i].type = RGB8;	// RGB8 standard type
	  fStack[i].buffer = NULL;	// invalid buffer
	  fStack[i].width = -1;		// invalid width
	  fStack[i].height = -1;	// invalid height
	}
	stackIsReady = true;  // mark init "done"
};

void framebufferFreeMemory()
{
	int i;
	for (i = 0; i < currentStackPosition; i++)
	{
	  if (fStack[currentStackPosition].buffer != NULL)	// check if memory is occupied
	  {
			free(fStack[currentStackPosition].buffer);	// release memory
		};
 	}
	currentStackPosition = 0;	// start allocating from position 0
	stackIsReady = false;  		// mark release "done"

	framebufferInitStack();		// re-init to startup state
}

int framebufferCreate(framebufferType type,	// type of framebuffer data
					  unsigned int width,	// width in pixels X
					  unsigned int height)	// height in pixels Y
{
	// fail if:
	ASSERT_GUARD(currentStackPosition+1 < MAX_STACK_SIZE &&		// slot position is invalid OR              -> stack position
				 (unsigned int)type < FRAMEBUFFERTYPE_EOL &&	// type is end/larger than list  OR         |<-
				 width != 0 &&									// width is less than 1 pixel OR            -> framebuffer dimensions
				 height != 0 &&									// height is less than 1 pixel OR           |
				 width <= MAX_PIXEL_SIZE &&						// width is larger than max pixel size OR   |
				 height <= MAX_PIXEL_SIZE,						// height is larger than max pixel size     |<-
				 "type:%i, width:%i, height:%i\n",
				 type, width, height);
				 
	// ensure a proper framebuffer stack initialization
	framebufferInitStack();

	// now, create the framebuffer...

	// save type of buffer and sizes
	fStack[currentStackPosition].type = type;
	fStack[currentStackPosition].width = width;
	fStack[currentStackPosition].height = height;
	
	// calculate byte-size of entire buffer
	int byteSize = -1;
	switch (type)
	{
		case RGB8:	 byteSize = sizeof(pixelRGB8);		break;
		case RGB32:	 byteSize = sizeof(pixelRGB32);		break;
		case RGBA8:	 byteSize = sizeof(pixelRGBA8);		break;
		case RGBA32: byteSize = sizeof(pixelRGBA32);	break;
	};
	
	fStack[currentStackPosition].buffer = calloc(width*height, byteSize);  	// allocate memory for framebuffer
	NULL_GUARD(fStack[currentStackPosition].buffer);
	
	return currentStackPosition++; // success!
};

// modify a framebuffer that was previously created
bool framebufferModify(unsigned int slot,	// slot of the framebuffer stack
					   unsigned int x,		// width/x pixel position
					   unsigned int y,		// height/y pixel position
					   fNumber R,			// RED value
					   fNumber G,			// GREEN value
					   fNumber B,			// BLUE value
					   fNumber A)			// Alpha value
{
	// fail if:
	ASSERT_GUARD(slot <= currentStackPosition &&	// slot is larger than amount of available framebuffer OR
				 fStack[slot].buffer != NULL &&		// framebuffer memory isn't allocated OR
				 x < fStack[slot].width &&			// x position is larger than framebuffer width OR
				 y < fStack[slot].height,			// y position is larger than framebuffer height
				 "slot:%i, x:%i, y:%i, R:%.3f, G:%.3f, B:%.3f, A:%.3f\n",
				 slot, x, y, R, G, B, A);

	// calculate array position
	unsigned int position = y * fStack[slot].width + x;
	
	// now modify the framebuffer
	switch (fStack[slot].type)
	{
		case RGB8:
		{
			// move to position
			pixelRGB8 *src = (pixelRGB8*)fStack[slot].buffer,
					  *pos = src + position;
		
			//BOUNDS_GUARD(pos, src, position);
			
			// color pixel+alpha
			pos->red = (unsigned char)R;
			pos->green = (unsigned char)G;
			pos->blue = (unsigned char)B;
		}
		break;

		case RGB32:
		{
			// move to position
			pixelRGB32 *src = (pixelRGB32*)fStack[slot].buffer,
			           *pos = src + position;
		
			//BOUNDS_GUARD(pos, src, position);
			
			// color pixel+alpha
			pos->red = R;
			pos->green = G;
			pos->blue = B;
		}
		break;
	
		case RGBA8:
		{
			// move to position
			pixelRGBA8 *src = (pixelRGBA8*)fStack[slot].buffer,
			           *pos = src + position;
		
			//BOUNDS_GUARD(pos, src, position);
			
			// color pixel+alpha
			pos->red = (unsigned char)R;
			pos->green = (unsigned char)G;
			pos->blue = (unsigned char)B;
			pos->alpha = (unsigned char)A;
		}
		break;

		case RGBA32:
		{
			// move to position
			pixelRGBA32 *src = (pixelRGBA32*)fStack[slot].buffer,
			            *pos = src + position;
		
			//BOUNDS_GUARD(pos, src, position);
			
			// color pixel+alpha
			pos->red = R;
			pos->green = G;
			pos->blue = B;
			pos->alpha = A;
		}
		break;			
	};

	return true; // success!
};

// retrieve raw access to a framebuffer that was previously created
framebuffer* framebufferGetFramebuffer(unsigned int slot)
{
	// fail if:
	ASSERT_GUARD(slot <= currentStackPosition, // slot is larger than amount of available framebuffer OR
	             "slot:%i",
				 slot);

	return &fStack[slot];
};
